﻿using System;
using System.Collections.Generic;

namespace Game
{
    public class AI
    {
        private const int k_MaxClueBufferSize = 5;

        private List<Clue> m_Clues;
        private bool m_IsInTurn;
        private int m_CurrentClueIndex;

        public AI()
        {
            resetAI();
        }

        public void RegisterAsListenerToCards(Board i_Board)
        {
            for (int i = 0; i < i_Board.Rows; ++i)
            {
                for (int j = 0; j < i_Board.Cols; ++j)
                {
                    i_Board.GetCard(new Board.Coordinate(i, j)).ChangedStatus += new EventHandler(card_ChangedStatus);
                }
            }
        }

        private void card_ChangedStatus(object sender, EventArgs e)
        {
            CardEventArgs cardArgs = e as CardEventArgs;

            if (cardArgs.Status == Card.eCardStatus.Visible)
            {
                registerCard(sender as Card, cardArgs.BoardPointer, cardArgs.Coordinates);
            }
        }

        private void registerCard(Card i_Card, Board i_Board, Board.Coordinate i_Coordinate)
        {
            int clueIndex;
            bool cardExistsInBuffer = isCardInClues(i_Card, out clueIndex);

            if (cardExistsInBuffer)
            {
                if (i_Board.IsCardBurned(i_Coordinate))
                {
                    m_Clues.RemoveAt(clueIndex);
                }
                else
                {
                    addCoordToClue(i_Coordinate, clueIndex);
                }
            }
            else if (!i_Board.IsCardBurned(i_Coordinate))
            {
                addNewClueToBuffer(i_Coordinate, i_Card);
            }            
        }
        
        private bool isCardInClues(Card i_Card, out int o_ClueIndex)
        {            
            bool cardExists = false;
            int i = 0;
            o_ClueIndex = 0;
           
            foreach (Clue currentClue in m_Clues)
            {
                if (currentClue.Card.Equals(i_Card))
                {
                    o_ClueIndex = i;
                    cardExists = true;
                }

                i++;
            }

            return cardExists;
        }

        private void addCoordToClue(Board.Coordinate i_Coordinate, int i_ClueIndex)
        {
            if (m_Clues[i_ClueIndex].NumberOfValidCoords == 1 && !m_Clues[i_ClueIndex].Coord1.Equals(i_Coordinate))
            {
                m_Clues[i_ClueIndex].Coord2 = i_Coordinate;
                m_Clues[i_ClueIndex].NumberOfValidCoords++;
            }
        }

        private void addNewClueToBuffer(Board.Coordinate i_Coordinate, Card i_Card)
        {
            if (m_Clues.Count < k_MaxClueBufferSize)
            {
                m_Clues.Add(new Clue(i_Card, i_Coordinate));
            }
        }

        public Board.Coordinate GetMove(Board i_Board, bool i_IsFirstMoveInTurn)
        {
            Board.Coordinate moveToMake;
            bool isFullClueExists;
            int fullClueIndex;

            refreshBuffer(i_Board);

            if (m_IsInTurn == false && i_IsFirstMoveInTurn)
            {
                isFullClueExists = lookForFullClue(out fullClueIndex);
                if (isFullClueExists)
                {
                    moveToMake = m_Clues[fullClueIndex].Coord1;
                    m_CurrentClueIndex = fullClueIndex;
                    m_IsInTurn = true;
                }
                else
                {
                    moveToMake = pickRandomCard(i_Board);
                }
            }
            else if (m_IsInTurn == true && i_IsFirstMoveInTurn == false)
            {
                moveToMake = m_Clues[m_CurrentClueIndex].Coord2;
                m_Clues.RemoveAt(m_CurrentClueIndex);
                m_IsInTurn = false;
                m_CurrentClueIndex = 0;
            }
            else
            {
               moveToMake = pickRandomCard(i_Board);
            }

            return moveToMake;
        }

        private void refreshBuffer(Board i_Board)
        {
            System.Collections.Generic.List<Clue> cluesToRemove = new System.Collections.Generic.List<Clue>();

            foreach (Clue currentClueInBuffer in m_Clues)
            {
                if (i_Board.IsCardBurned(currentClueInBuffer.Coord1)) 
                {
                    cluesToRemove.Add(currentClueInBuffer);
                }
            }

            foreach (Clue currentClueToRemove in cluesToRemove)
            {
                m_Clues.Remove(currentClueToRemove);
            }
        }

        private bool lookForFullClue(out int o_FullClueIndex)
        {
            int i = 0;
            bool fullClueExists = false;
            int fullClueIndex = -1;

            foreach (Clue currentClue in m_Clues)
            {
                if (currentClue.NumberOfValidCoords == 2)
                {
                    fullClueExists = true;
                    fullClueIndex = i;       
                }

                i++;
            }

            o_FullClueIndex = fullClueIndex;

            return fullClueExists;
        }

        private Board.Coordinate pickRandomCard(Board i_Board)
        {
            List<Board.Coordinate> legalMovesPool = i_Board.PoolLegalMoves();
            
            if (legalMovesPool.Count == 0)
            {
                throw new Exception("No legal cards are available.");
            }

            System.Random random = new System.Random();
            int randomIndex = random.Next(0, legalMovesPool.Count);

            return legalMovesPool[randomIndex];
        }

        private void resetAI()
        {
            m_Clues = new System.Collections.Generic.List<Clue>();
            m_IsInTurn = false;
            m_CurrentClueIndex = 0;        
        }

        private class Clue
        {
            private int m_NumberOfValidCoords;
            private Board.Coordinate m_Coord1, m_Coord2;
            private Card m_Card;

            public Clue(Card i_Card, Board.Coordinate i_Coord1)
            {
                m_NumberOfValidCoords = 1;
                m_Coord1 = i_Coord1;
                m_Coord2 = Board.Coordinate.MakeEmpty();
                m_Card = i_Card;
            }

            public int NumberOfValidCoords
            {
                get { return m_NumberOfValidCoords; }
                set { m_NumberOfValidCoords = value; }
            }

            public Board.Coordinate Coord1
            {
                get { return m_Coord1; }
                set { m_Coord1 = value; }
            }

            public Board.Coordinate Coord2
            {
                get { return m_Coord2; }
                set 
                {
                    if (value.Equals(m_Coord1))
                    {
                        throw new Exception("Clue must have different two different coords");
                    }

                    m_Coord2 = value; 
                }
            }

            public Card Card
            {
                get { return m_Card; }
                set { m_Card = value; }
            }
        }
    }
}
